# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: xguo

import coin.exchange.base.kr_rest.private_client_base as privcb
import coin.exchange.liqui_v3.kr_rest.native_private_client as nprivc
from coin.exchange.liqui_v3.kr_rest.currency import LiquiCurrency
from coin.exchange.liqui_v3.kr_rest.product import LiquiProduct

import coin.proto.coin_order_enums_pb2 as coin_order
from coin.proto.coin_query_pb2 import (AccountBalance,
                                       CurrencyBalance,
                                       ProductFillElement,
                                       ProductFillBundle,
                                       ProductOrderElement,
                                       ProductOrderBundle)


def _get_side(side):
  side_map = {
      'buy': coin_order.BUY_ORDER,
      'sell': coin_order.SELL_ORDER,
  }
  if side not in side_map:
    raise ValueError('Invalid side')
  return side_map[side]


class LiquiPrivateParser(object):
  @staticmethod
  def parse_balance(update_msg):
    """
    {
      "success":1,
      "return":{
      "funds":{
          "eth":325,
          "btc":23.998,
          "ltc":0,
          ...
          },
      "rights":{
          "info":1,
          "trade":0,
          "withdraw":0
          },
      "transaction_count":0,
          "open_orders":1,
           "server_time":1342123547
          }
    }"""
    assert update_msg['success'] == 1

    currency_balances = []
    for native_currency, balance in update_msg['return']['funds'].items():
      currency = LiquiCurrency.FromStrNativeCurrencyNothrow(native_currency)
      if currency is None:
        continue
      balance = CurrencyBalance(
          currency=currency.currency,
          currency_native=currency.native_currency,
          total=balance,
          available=balance,
      )
      currency_balances.append(balance)
    return AccountBalance(
        exchange='Liqui',
        each_balance=currency_balances,
    )

  @staticmethod
  def parse_fill(update_msg, product):
    """
    {
    "success":1,
    "return":{
        "166830":{
            "pair":"eth_btc",
            "type":"sell",
            "amount":1,
            "rate":450,
            "order_id":343148,
            "is_your_order":1,
            "timestamp":1342445793
        }
    }}
    """
    currency_fill_list = []
    for trade_id, trade_info in update_msg['return']:
      if trade_info['pair'] != product.native_symbol:
        continue
      if trade_info['is_your_order'] != 1:
        continue

      side = _get_side(trade_info['type'])
      currency_fill = ProductFillElement(symbol=product.symbol,
                                         price=float(trade_info['rate']),
                                         qty=float(trade_info['amount']),
                                         side=side,
                                         order_id=str(trade_info['order-id']),
                                         fill_timestamp=int(trade_info['timestamp'] * 1e+9))
      currency_fill_list.append(currency_fill)
    return ProductFillBundle(exchange='Liqui', symbol=product.symbol, each_fill=currency_fill_list)

  @staticmethod
  def parse_orders(update_msg, product):
    """
    {
      "success": 1,
      "return": {
        "343152": {
          "pair": "eth_btc",
          "type": "sell",
          "amount": 12.345,
          "rate": 485,
          "timestamp_created": 1342448420,
          "status": 0
        },
        ...
      }"""
    assert update_msg['success'] == 1
    currency_order_list = []
    for order_id, order_info in update_msg['return'].items():
      assert product.native_symbol == order_info['pair']
      side = _get_side(order_info['type'])
      currency_order = ProductOrderElement(symbol=product.symbol,
                                           side=side,
                                           price=float(order_info['rate']),
                                           qty=float(order_info['amount']),
                                           order_id=str(order_id),
                                           created_timestamp=int(order_info['created-at'] * 1e+9))
      currency_order_list.append(currency_order)
    return ProductOrderBundle(exchange='Liqui',
                              symbol=product.symbol,
                              each_order=currency_order_list)


class LiquiPrivateClient(privcb.PrivateClientBase):
  ProductType = LiquiProduct

  def __init__(self, key_file):
    privcb.PrivateClientBase.__init__(self)
    self.nprivc = nprivc.LiquiNativePrivateClient(key_file=key_file)

  def query_account_balance_impl(self):
    update = self.nprivc.query_getinfo()
    update.msg = LiquiPrivateParser.parse_balance(update.msg)
    return update

  def query_fills_impl(self, product):
    update = self.nprivc.query_trade_history(product.native_symbol)
    update.msg = LiquiPrivateParser.parse_fill(update.msg, product)
    return update

  def query_list_orders_impl(self, product):
    update = self.nprivc.query_active_orders(product.native_symbol)
    update.msg = LiquiPrivateParser.parse_orders(update.msg, product)
    return update

  def query_submit_impl(self, product, side, price, qty):
    assert side in ('sell', 'buy')
    update = self.nprivc.query_trade(product.native_symbol, side, price, qty)
    return update

  def query_cancel_impl(self, product, order_id):
    update = self.nprivc.query_cancel_order(order_id)
    return update

  def query_cancel_product_impl(self, product):
    raise NotImplementedError()
